package dataStructures.index;

import java.io.FileInputStream;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;


import org.apache.xerces.parsers.SAXParser;

import org.xml.sax.helpers.DefaultHandler;


import dataStructures.LoggedDast;

import utils.MyLogger;
import utils.TimeCounter;
import xml.IdToRouteIndexHandler;

/*
 * this class will hold the mapping from node id to it's route from the root to it
 */
public class IdToRouteIndex extends LoggedDast implements Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 7322501976267775696L;

	/*
	 * will hold the mapping between node's id to the route's id
	 */
	private ArrayList<Integer> _nodeIdToRoutIdMapping;
	
	/*
	 *  will hold the mapping between serialized route to the route's index 
	 */
	//private HashMap<Integer,ArrayList<Integer>> _routeIdToSerializedRouteMap;
	private ArrayList<ArrayList<Integer>> _routeIdToSerializedRouteMapping;
	
	/*
	 * will hold the mapping between the labelId to the label's name
	 */
	//private HashMap<Integer,String> _labelIdToLabelMap;
	private ArrayList<String> _labelIdToLabelMapping;
	
	public IdToRouteIndex()
	{
		_nodeIdToRoutIdMapping = new ArrayList<Integer>();
		_routeIdToSerializedRouteMapping = new ArrayList<ArrayList<Integer>>();
		_labelIdToLabelMapping = new ArrayList<String>();
	}
	
	public int getNumberOfNodes()
	{
		return _nodeIdToRoutIdMapping.size();
	}
	
	public ArrayList<String> DeserializeRoute(ArrayList<Integer> serializedRoute)
	{
		ArrayList<String> deserializedRoute = new ArrayList<String>();
		
		for(int labelId : serializedRoute)
		{
			deserializedRoute.add(_labelIdToLabelMapping.get(labelId));
		}
		
		return deserializedRoute;
	}
	
	public void printRouts()
	{
		for(int nodeId = 0 ; nodeId < _nodeIdToRoutIdMapping.size() ; ++nodeId)
		{
			ArrayList<Integer> serializedRoute = _routeIdToSerializedRouteMapping.get(_nodeIdToRoutIdMapping.get(nodeId));
			System.out.println("Id = " + (nodeId+1) + "  Route (" + _nodeIdToRoutIdMapping.get(nodeId) + ") : " + DeserializeRoute(serializedRoute));
		}
	}
	
	public ArrayList<Integer> GetRouteIndex(int id)
	{
		return _routeIdToSerializedRouteMapping.get(_nodeIdToRoutIdMapping.get(id));
	}
	
	public ArrayList<String> GetRouteLabels(int id)
	{
		return DeserializeRoute(GetRouteIndex(id));
	}
	/*
	 * this function will add a given XML to this index. 
	 * be aware that if this index is not empty this is the user's responsibility 
	 * to load other XMLs with the same structure as the previous
	 */
	
	public boolean LoadXml(String xmlPath)
	{

		TimeCounter timeCounter = new TimeCounter();

		try
		{

			AddLogEntry("Starting building IdToRouteIndex for " + xmlPath);
			//SAXParserFactory factory = SAXParserFactory.newInstance();
			//SAXParser saxParser = factory.newSAXParser();
			SAXParser saxParser = new SAXParser();
			DefaultHandler handler = new IdToRouteIndexHandler(_nodeIdToRoutIdMapping,_routeIdToSerializedRouteMapping,_labelIdToLabelMapping);
			saxParser.setContentHandler(handler);
			saxParser.parse(xmlPath);
			
			//saxParser.parse(xmlPath,handler);
		 	
		}
		catch (Exception e)
		{
			AddLogEntry("Error while building index:  " + e.getMessage());
			return false;
		}
		AddLogEntry("Index build complete with " + String.valueOf(_nodeIdToRoutIdMapping.size()) + " nodes in " + String.valueOf(timeCounter.Toc()) + " ms");
		return true;
		
	}
	

	//methods for serialization
	@SuppressWarnings("unchecked")
	public static IdToRouteIndex Load(InputStream stream) throws Exception
    {
		
		ObjectInputStream s = new ObjectInputStream(stream);
		IdToRouteIndex index = new IdToRouteIndex();
		HashMap<String, Integer> labelToLabelIdMap = (HashMap<String, Integer>)s.readObject();
		
		index._labelIdToLabelMapping = new ArrayList<String>(labelToLabelIdMap.size());
		for(int i = 0 ; i < labelToLabelIdMap.size() ; ++i)
		{
			index._labelIdToLabelMapping.add("");
		}
		
		for(String label : labelToLabelIdMap.keySet())
		{
			index._labelIdToLabelMapping.set(labelToLabelIdMap.get(label), label);
		}
		
		int numberOfNodes = (Integer)s.readObject();
		int numberOfRouts = (Integer)s.readObject();
		
		index._nodeIdToRoutIdMapping = new ArrayList<Integer>(numberOfNodes);
		for(int i = 0 ; i < numberOfNodes ; ++i)
		{
			index._nodeIdToRoutIdMapping.add(-1);
		}
		
		for(int r = 0 ; r < numberOfRouts ; ++r)
		{
			
			ArrayList<Integer> route = (ArrayList<Integer>)s.readObject();
			ArrayList<Integer> nodes = (ArrayList<Integer>)s.readObject();
			
			int routeId = index._routeIdToSerializedRouteMapping.size();
			index._routeIdToSerializedRouteMapping.add(route);
			
			for(int n : nodes)
			{
				index._nodeIdToRoutIdMapping.set(n,routeId);
			}
		}
		return index;
		
		/*IdToRouteIndex index = new IdToRouteIndex();
		index._labelIdToLabelMapping = (ArrayList<String>)s.readObject();
		int[] arr = (int[])s.readObject();
		
		int arrIndex = 0;
		
		while(arrIndex < arr.length)
		{
			int routeLength = arr[arrIndex++];
			ArrayList<Integer> route = new ArrayList<Integer>(routeLength);
			for(int i = 0 ; i < routeLength;++i,++arrIndex)
			{
				route.add(arr[arrIndex]);
			}
			
			int idsLength = arr[arrIndex++];
			ArrayList<Integer> ids = new ArrayList<Integer>(idsLength);
			for(int i = 0 ; i < idsLength ; ++i,++arrIndex)
			{
				ids.add(arr[arrIndex]);
			}
			
			int routeId = index._routeIdToSerializedRouteMap.size();
			
			//put the route itself
			index._routeIdToSerializedRouteMap.put(routeId, route);
			
			//connect each node which can be reached via this route with the routeID
			for(int nodeId : ids)
			{
				index._nodeIdToRoutIdMapping.ensureCapacity(nodeId+1);
				index._nodeIdToRoutIdMapping.add(nodeId,routeId);
				//index._nodeIdToRoutIdMap.put(nodeId, routeId);
			}
			
		}
		
		return index;
		*/
    }
	
	//methods for serialization
	public static IdToRouteIndex Load(InputStream stream,MyLogger logger) throws Exception
    {
		TimeCounter timeCounter = new TimeCounter();
		logger.AddEntry("loading IdToRouteIndex from stream");
		IdToRouteIndex index = Load(stream);
		logger.AddEntry("loading complete in " + String.valueOf(timeCounter.Toc()) + " ms");
		return index;
    }
	/*
	private int GetNumberOfElements(HashMap<ArrayList<Integer>,ArrayList<Integer>> routeToIdsMap)
	{
		int n = 0;
		for(ArrayList<Integer> key:routeToIdsMap.keySet())
		{
			n+=(key.size()+1);
			n+=(routeToIdsMap.get(key).size()+1);
		}
		
		return n;
	}
	
	private int SerializeArr(int[] arr,int index,ArrayList<Integer> arrList)
	{
		arr[index++] = arrList.size();
		
		for(int val:arrList)
		{
			arr[index++] = val;
		}
		
		return arrList.size()+1;
	}
	*/
	/*
	private int[] SerializeLabelToLabelIDMap(HashMap<ArrayList<Integer>,ArrayList<Integer>> routeToIdsMap)
	{
		//ArrayList<Integer> arr;
		int[] arr = new int[GetNumberOfElements(routeToIdsMap)];
		int index = 0;
		for(ArrayList<Integer> key:routeToIdsMap.keySet())
		{
			index+=SerializeArr(arr, index, key);
			index+=SerializeArr(arr, index, routeToIdsMap.get(key));
		}
		
		return arr;
	}
	*/
	
	/*
	private void PackAndStoreRoutes(ObjectOutputStream s) throws Exception
	{
		/*HashMap<ArrayList<Integer>,ArrayList<Integer>> routeToIdsMap = new HashMap<ArrayList<Integer>, ArrayList<Integer>>();
		for(Entry<Integer,Integer> e : _nodeIdToRoutIdMap.entrySet())
		{
			ArrayList<Integer> route = _routeIdToSerializedRouteMap.get(e.getValue());
			
			if (!routeToIdsMap.containsKey(route))
			{
				ArrayList<Integer> ids = new ArrayList<Integer>();
				ids.add(e.getKey());
				routeToIdsMap.put(route,ids);
			}
			else
			{
				routeToIdsMap.get(route).add(e.getKey());
			}
		}
		
		s.writeObject(SerializeLabelToLabelIDMap(routeToIdsMap));
		*/
		
	//}

	public void Store(OutputStream stream) throws Exception
	{
		TimeCounter timeCounter = new TimeCounter();
		AddLogEntry("storing IdToRouteIndex from stream");
		ObjectOutputStream s = new ObjectOutputStream(stream);
		s.writeObject(_labelIdToLabelMapping);
		s.writeObject(_routeIdToSerializedRouteMapping);
		s.writeObject(_nodeIdToRoutIdMapping);
		//s.writeObject(_labelIdToLabelMapping);
		//s.writeObject(_nodeIdToRoutIdMap);
		//PackAndStoreRoutes(s);
		//s.writeObject(this);
		s.flush();
		AddLogEntry("storing complete in " + String.valueOf(timeCounter.Toc()) + " ms");
	}
	
	/**
	 * this is a baisc tester
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception{
		
		
		IdToRouteIndex idx = new IdToRouteIndex();
		idx.AttachLogger(new MyLogger("D:\\development\\dblab\\dblp.log"));
		idx.LoadXml("D:\\development\\dblab\\xmlDocs\\dblp.xml");
		System.out.println("Done!");
		FileOutputStream fos = new FileOutputStream("D:\\development\\dblab\\dblp.idx");
		idx.Store(fos);
		
		fos.close();
		
		//idx.printRouts();
		//FileOutputStream fos = new FileOutputStream("D:\\development\\dblab\\dblp.idx");
		//idx.Store(fos);
		
		//System.out.println("Done!!!");
		
		/*FileInputStream fis = new FileInputStream("D:\\development\\dblab\\t.idx");
		IdToRouteIndex newIdx = IdToRouteIndex.Load(fis,new MyLogger("D:\\development\\dblab\\dblplog.log"));
		System.out.println("Done!!! " + newIdx.getNumberOfNodes());
		newIdx.printRouts();*/
		/*System.out.println(newIdx.getNumberOfNodes());*/
		//idx.LoadXml("D:\\development\\dblab\\file.xml");
		//idx.printRouts();
	}

}
